Tento dokument shrnuje diskuzi o různých architektonických přístupech k vytváření XML dokumentů v XmlStackBuilder, zejména s ohledem na použití z FoxPro přes wwDotnetBridge.
Princip:
builder spravuje interní zásobníkPush() přidávají elementy na zásobník, Pop() je odebíráPříklad použití (FoxPro):
LOCAL loBridge, loBuilder
loBridge = getwwBridge()
loBridge.LoadAssembly("XmlStackBuilder.Core.dll")
loBuilder = loBridge.CreateInstance("XmlStackBuilder.Core.XmlStackBuilder")
loBuilder.Push("Hlavicka")
loBuilder.PushValue("mesic", "12")
loBuilder.PushValue("rok", "2024")
loBuilder.Pop() && ukončí Hlavicka
lcXml = loBuilder.Build()
Výhody pro FoxPro:
Nevýhody:
Princip:
Příklad použití (C#):
var root = new XmlElement("Hlavicka");
var mesic = new XmlElement("mesic");
mesic.SetValue("12");
root.AddChild(mesic);
var rok = new XmlElement("rok");
rok.SetValue("2024");
root.AddChild(rok);
string xml = root.ToXml();
Příklad použití (FoxPro):
LOCAL loBridge, loRoot, loMesic, loRok
loBridge = getwwBridge()
loBridge.LoadAssembly("XmlStackBuilder.Core.dll")
loRoot = loBridge.CreateInstance("XmlStackBuilder.Core.XmlElement", "Hlavicka")
* Vytvoření child elementu
loMesic = loBridge.CreateInstance("XmlStackBuilder.Core.XmlElement", "mesic")
loMesic.SetValue("12")
loRoot.AddChild(loMesic)
* Další child element
loRok = loBridge.CreateInstance("XmlStackBuilder.Core.XmlElement", "rok")
loRok.SetValue("2024")
loRoot.AddChild(loRok)
lcXml = loRoot.ToXml()
Výhody:
Nevýhody pro FoxPro:
* Toto NEFUNGUJE ve FoxPro:
loRoot.AddChild("mesic").SetValue("12")
* Musíte použít:
loMesic = loRoot.AddChild("mesic")
loMesic.SetValue("12")
Princip:
Příklad použití (C#):
var xml = new FluentXmlBuilder()
.WithRoot("Dphdp3")
.WithHlavicka(h => {
h.Mesic(12);
h.Rok(2024);
h.DatVytvor(DateTime.Now);
})
.WithPolozky(p => {
p.AddPolozka(pol => {
pol.Castka(1500.50m);
pol.Popis("Test");
});
})
.Build();
Důvody, proč NELZE použít z FoxPro:
FoxPro nemá lambda výrazy
// C# lambda - neexistuje ekvivalent ve FoxPro
.WithHlavicka(h => { h.Mesic(12); })
FoxPro nepodporuje Action
// Tento typ parametru není přenositelný přes COM
public FluentXmlBuilder WithHlavicka(Action<HlavickaBuilder> configure)
FoxPro nemá generické typy
// Generika nefungují přes COM interop
public class FluentXmlBuilder<T> where T : class
Method chaining nefunguje přes COM
* Toto NEFUNGUJE ve FoxPro:
loBuilder.WithRoot("Test").WithHlavicka(...).Build()
Výhody (pouze pro C#):
Nevýhody:
| Vlastnost | Stack API | Object Tree | Fluent Builder |
|---|---|---|---|
| Použitelnost z FoxPro | ✅ Ano | ⚠️ Možné, ale pomalé | ❌ Ne |
| Počet COM objektů | 1 (builder) | Mnoho (každý element) | N/A |
| Method chaining | Nepotřebuje | Nefunguje v FoxPro | Nefunguje v FoxPro |
| Lambda výrazy | Nepotřebuje | Nepotřebuje | Vyžaduje (FoxPro nemá) |
| Výkon přes COM | ⚡ Rychlé | 🐌 Pomalé | N/A |
| Čitelnost kódu | Dobrá | Verbose | N/A |
Používejte Stack API - je to jediný praktický přístup pro FoxPro:
Můžete použít všechny tři přístupy:
Stack API je optimální volba pro XmlStackBuilder, protože:
FoxPro omezení, která vylučují alternativní přístupy:
Stack API je jediná architektura, která splňuje požadavky pro použití z FoxPro přes wwDotnetBridge.
Kromě Stack API pro sestavování XML nabízí XmlStackBuilder další API pro generování JSON šablon, renderování výstupů a konverzi FRX reportů.
Automatické generování JSON šablon pro tabulkové sestavy z definice FoxPro kurzoru — bez nutnosti psát JSON ručně.
| Metoda | Vstup | Výstup |
|---|---|---|
GenerateColumnsFromSchema |
cursor schema + options | JSON fragment s columns + printColumns |
GenerateReportFromSchema |
output path + cursor schema + options | kompletní JSON soubor s report, groups, sumExpressions |
*--- CREATE TABLE formát ---
lcSchema = "CREATE TABLE U (Den D, Rada C(2), Doklad N(5), Castka N(15,2), Text C(30))"
*--- Plain formát ---
lcSchema = "Den D,Rada C(2),Doklad N(5),Castka N(15,2),Text C(30)"
*--- S FoxPro line continuation ---
lcSchema = "CREATE TABLE U (Den D;" + CHR(13) + CHR(10) + " , Rada C(2))"
Podporované typy: C/Character, N/Numeric, D/Date, T/DateTime, L/Logical, M/Memo, I/Integer, B/Double, Y/Currency, V/Varchar, F/Float.
Formát: "key1:value1,key2:value2,..." — split na , (respektuje {}), pak na první :.
| Klíč | Default | Popis |
|---|---|---|
dataFontPt |
8 |
Velikost fontu datových buněk (pt) |
pageSize |
A4 |
Formát papíru: A4, A3 |
orientation |
auto |
portrait, landscape, auto (zkusí portrait, pak landscape) |
maxDecimal |
2 |
Max počet desetinných míst |
maxLenString |
20 |
Max šířka textových sloupců (znaky) |
groups |
(prázdný) | Skupiny: stredisko;ucet+organizace;sn |
noTotalColumns |
(prázdný) | Sloupce bez součtů: id;doklad;radek |
element |
data |
Název XML elementu se záznamy |
title |
(prázdný) | Nadpis sestavy (podporuje {element.field}) |
tfiltr |
(prázdný) | Filtr sestavy (podporuje {element.field}) |
culture |
cs-CZ |
Kultura pro formátování |
rowNumbers |
false |
Číslování řádků: false, global, group |
Syntax: groups:stredisko;ucet+organizace;sn
; odděluje skupiny (od vnější k vnitřní)+ odděluje compound klíče ve skupině (sloučí se do pole groupExpression)headerMode: "external", tableBreak: trueheaderMode: "inline", tableBreak: false"Stredisko {stredisko}" — Proper(fieldName) + placeholder"Celkem za {stredisko}"noTotalColumnsŠířky se počítají v mm podle vzorce inspirovaného sfReport/makerepo2:
charWidthMm = fontPt × 0.6 × 25.4 / 72.0
gapMm = 2 × (0.5 - 0.015 × fontPt) × charWidthMm
columnMm = widthChars × charWidthMm + gapMm
Šířky v znacích podle typu:
| Typ | Šířka (znaky) | Poznámka |
|---|---|---|
D (Date) |
8.5 | dd.MM.yyyy |
T (DateTime) |
14 | dd.MM.yyyy HH:mm |
L (Logical) |
3 | Ano/Ne |
C (Char) |
min(max(width, 2), maxLenString) | Krátké (≤4) × 1.2 |
N (Numeric) |
picture 999 999.99 |
Korekce: mezery×0.35, tečky×0.45 |
I (Integer) |
min(9, width) | S tisícovým formátem |
Y (Currency) |
10+2 | 999 999 999.99 |
Finální šířka = max(dataWidthMm, headerWidthMm).
Dostupná šířka stránky (po odečtení 2×8mm margins):
| Papír | Portrait | Landscape |
|---|---|---|
| A4 | 194 mm | 281 mm |
| A3 | 281 mm | 404 mm |
Sloupce se přidávají zleva dokud kumulativní šířka nepřesáhne dostupnou šířku. Pokud se nevejdou všechny → "printColumns" v JSON.
Auto orientation ("auto"): zkusí portrait, pokud se nevejdou → landscape.
*--- 1. Fragment columns (pro vložení do vlastní šablony) ---
lcColumns = loBuilder.GenerateColumnsFromSchema( ;
"Den D,Rada C(2),Castka N(15,2)", ;
"dataFontPt:8")
* Vrátí JSON:
* {
* "columns": {
* "den": { "label": "Den", "align": "center", "format": "dd.MM.yyyy", "width": "13.8mm" },
* "rada": { "label": "Rada", "width": "7.1mm" },
* "castka": { "label": "Castka", "align": "right", "format": "N2", "hideZero": true, "width": "25.6mm" }
* }
* }
*--- 2. Kompletní report s groups a placeholdery ---
loBuilder.GenerateReportFromSchema( ;
"C:\output\report.json", ;
"CREATE TABLE U (Den D, Rada C(2), Stredisko C(3), Ucet C(3), " + ;
"Castka N(15,2), Cizi N(15,2), Text C(30))", ;
"dataFontPt:7" + ;
",groups:stredisko;ucet" + ;
",noTotalColumns:den;rada;stredisko;ucet;text" + ;
",title:{header.nadpis}" + ;
",tfiltr:{header.filtr}" + ;
",element:doklady")
* Vygeneruje kompletní JSON šablonu:
* - renderer: "table"
* - report: title, culture, dataFontPt, pageLayout, pageMarginMm
* - header: filter s placeholderem
* - pageHeader/pageFooter: {company}, {title}, {page}/{pages}, {date}
* - sections: element, columns s šířkami, 2 groups se sumExpressions, grand total
* - printColumns (pokud se nevejdou všechny)
*--- 3. Saldokonto s auto orientací ---
loBuilder.GenerateReportFromSchema( ;
"C:\output\saldo.json", ;
"Den D,Organizace C(15),Doklad C(10),MD N(15,2),D N(15,2),Saldo N(15,2)", ;
"dataFontPt:6,orientation:auto,groups:organizace,noTotalColumns:den;doklad,element:pohyby")
*--- 4. Jednoduchý ceník bez skupin ---
loBuilder.GenerateReportFromSchema( ;
"C:\output\cenik.json", ;
"Kod C(10),Nazev C(40),Mj C(5),Cena N(12,2)", ;
"noTotalColumns:kod;nazev;mj,element:polozky,title:Ceník zboží")
*--- 5. Široká sestava s compound skupinou ---
loBuilder.GenerateReportFromSchema( ;
"C:\output\analyza.json", ;
"Stredisko C(3),Ucet C(6),Organizace C(10),Doklad C(10),Text C(30)," + ;
"MD N(15,2),D N(15,2),Zustatek N(15,2)", ;
"dataFontPt:6" + ;
",orientation:landscape" + ;
",pageSize:A4" + ;
",groups:stredisko;ucet+organizace" + ;
",noTotalColumns:doklad;text" + ;
",element:pohyby" + ;
",culture:cs-CZ" + ;
",rowNumbers:group")
* Výsledek: compound skupina ucet+organizace →
* "groupExpression": ["ucet", "organizace"]
* "label": "Ucet + Organizace {ucet} {organizace}"
{
"renderer": "table",
"report": {
"title": "{header.nadpis}",
"culture": "cs-CZ",
"dataFontPt": 7,
"pageLayout": "paged",
"pageMarginMm": 8
},
"header": {
"filter": "{header.filtr}"
},
"pageHeader": {
"left": "{company}",
"center": "{title}",
"right": "Strana {page} z {pages}",
"heightMm": 10,
"fromPage": 2
},
"pageFooter": {
"left": "Vytištěno: {date}",
"heightMm": 8
},
"sections": [
{
"element": "doklady",
"columns": {
"den": { "label": "Den", "align": "center", "format": "dd.MM.yyyy", "width": "13.8mm" },
"rada": { "label": "Rada", "width": "7.1mm" },
"stredisko": { "label": "Stredisko", "width": "14.5mm" },
"ucet": { "label": "Ucet", "width": "7.1mm" },
"castka": { "label": "Castka", "align": "right", "format": "N2", "hideZero": true, "width": "25.6mm" },
"cizi": { "label": "Cizi", "align": "right", "format": "N2", "hideZero": true, "width": "25.6mm" },
"text": { "label": "Text", "width": "30.8mm" }
},
"groups": [
{
"groupExpression": "stredisko",
"label": "Stredisko {stredisko}",
"headerMode": "external",
"tableBreak": true,
"sumExpressions": [
{ "field": "castka", "expr": "SUM(castka)", "format": "N2" },
{ "field": "cizi", "expr": "SUM(cizi)", "format": "N2" }
],
"sumLabel": "Celkem za {stredisko}"
},
{
"groupExpression": "ucet",
"label": "Ucet {ucet}",
"headerMode": "inline",
"tableBreak": false,
"sumExpressions": [
{ "field": "castka", "expr": "SUM(castka)", "format": "N2" },
{ "field": "cizi", "expr": "SUM(cizi)", "format": "N2" }
],
"sumLabel": "Celkem za {ucet}"
}
],
"sumExpressions": [
{ "field": "castka", "expr": "SUM(castka)", "format": "N2" },
{ "field": "cizi", "expr": "SUM(cizi)", "format": "N2" }
],
"sumLabel": "Celkem"
}
],
"_info": "Vygenerováno z cursor schema. Upravte element, labels a formáty dle potřeby."
}
Šířky se počítají podle vzorce:
charWidthMm = fontPt × 0.6 × 25.4 / 72
gapMm = 2 × (0.5 − 0.015 × fontPt) × charWidthMm
paddingMm = 2.1 (CSS: 2×4px padding, fixed)
columnMm = effWidth × charWidthMm + gapMm + paddingMm
Padding 2.1 mm = CSS td{padding:2px 4px} s border-collapse:collapse. Je uvnitř šířky sloupce, nezávisí na fontu. Korekce pro numerické sloupce: mezera (tisíce) = 0.35 znaku, tečka/čárka (des.) = 0.45 znaku.
Konstanty:
| Font | charWidthMm | gapMm | gap+padding |
|---|---|---|---|
| 6 pt | 1.270 | 1.04 | 3.14 |
| 7 pt | 1.482 | 1.17 | 3.27 |
| 8 pt | 1.693 | 1.29 | 3.39 |
| 9 pt | 1.905 | 1.39 | 3.49 |
Šířky sloupců:
| Typ | Obraz (picture) | effWidth | 6 pt | 7 pt | 8 pt | 9 pt |
|---|---|---|---|---|---|---|
| Date | dd.MM.yyyy |
8.50 | 13.9 | 15.9 | 17.8 | 19.7 |
| DateTime | dd.MM.yyyy HH:mm |
14.00 | 20.9 | 24.0 | 27.1 | 30.2 |
| L | A/N |
3.00 | 7.0 | 7.7 | 8.5 | 9.2 |
| N0 (3) | 999 |
3.00 | 7.0 | 7.7 | 8.5 | 9.2 |
| N0 (6) | 999 999 |
6.65 | 11.6 | 13.1 | 14.6 | 16.2 |
| N0 (9) | 999 999 999 |
10.30 | 16.2 | 18.5 | 20.8 | 23.1 |
| N0 (12) | 999 999 999 999 |
13.95 | 20.9 | 23.9 | 27.0 | 30.1 |
| N2 (3) | 999,99 |
5.55 | 10.2 | 11.5 | 12.8 | 14.1 |
| N2 (6) | 999 999,99 |
9.20 | 14.8 | 16.9 | 19.0 | 21.0 |
| N2 (9) | 999 999 999,99 |
12.85 | 19.5 | 22.3 | 25.1 | 28.0 |
| N2 (12) | 999 999 999 999,99 |
16.50 | 24.1 | 27.7 | 31.3 | 34.9 |
| N3 (3) | 999,999 |
6.55 | 11.5 | 13.0 | 14.5 | 16.0 |
| N3 (6) | 999 999,999 |
10.20 | 16.1 | 18.4 | 20.7 | 22.9 |
| N3 (9) | 999 999 999,999 |
13.85 | 20.7 | 23.8 | 26.8 | 29.9 |
| N3 (12) | 999 999 999 999,999 |
17.50 | 25.4 | 29.2 | 33.0 | 36.8 |
| C(4) | xxxx (×1.2) |
4.80 | 9.2 | 10.4 | 11.5 | 12.6 |
| C(6) | xxxxxx |
6.00 | 10.8 | 12.2 | 13.5 | 14.9 |
| C(10) | xxxxxxxxxx |
10.00 | 15.8 | 18.1 | 20.3 | 22.5 |
| C(20) | 20 znaků | 20.00 | 28.5 | 32.9 | 37.3 | 41.6 |
| C(30) | 30 znaků | 30.00 | 41.2 | 47.7 | 54.2 | 60.6 |
| C(40) | 40 znaků | 40.00 | 53.9 | 62.5 | 71.1 | 79.7 |
| C(50) | 50 znaků | 50.00 | 66.6 | 77.4 | 88.1 | 98.7 |
Dostupná šířka stránky (po odečtení 2×8 mm okrajů):
| Formát | Portrait | Landscape |
|---|---|---|
| A4 | 194 mm | 281 mm |
| A3 | 281 mm | 404 mm |
Poznámka:
CursorSchemaGeneratorstandardně ořezává textové sloupce namaxLenString=20znaků. C(30)–C(50) se tedy v generovaném JSON zobrazí jako 20 znaků, pokud nenastavítemaxLenString:30(nebo vyšší) v options.
Při změně dataFontPt je potřeba přepočítat "width" na sloupcích.
Přesný vzorec (padding = fixní, content + gap = škálují se):
paddingMm = 2.1
width_nové = paddingMm + (width_staré - paddingMm) × (fontPt_nové / fontPt_staré)
Zjednodušený vzorec (chyba < 0.5 mm pro sloupce > 10 mm):
width_nové ≈ width_staré × (fontPt_nové / fontPt_staré)
Přepočítávací koeficienty:
| Z \ Na | 6 pt | 7 pt | 8 pt | 9 pt |
|---|---|---|---|---|
| 6 pt | — | 1.167 | 1.333 | 1.500 |
| 7 pt | 0.857 | — | 1.143 | 1.286 |
| 8 pt | 0.750 | 0.875 | — | 1.125 |
| 9 pt | 0.667 | 0.778 | 0.889 | — |
Koeficienty = prostý poměr fontů (fontPt_nové / fontPt_staré): 7/8 = 0.875, 6/8 = 0.75 atd. Přesný vzorec s paddingem se vyplatí u úzkých sloupců (< 12 mm).
1. FoxPro: loBuilder.GenerateReportFromSchema("sablona.json", lcSchema, lcOptions)
2. Uživatel: otevře sablona.json ve VS Code, upraví labels, přidá lookup, vizuální úpravy
3. FoxPro: loBuilder.Render("sestava.html", "sablona.json") && nebo .pdf / .xlsx
*--- XML z interního builderu ---
loBuilder.Render("sestava.html", "definice.json") && HTML výstup
loBuilder.Render("sestava.pdf", "definice.json") && PDF výstup
loBuilder.Render("sestava.xlsx", "definice.json") && XLSX výstup
loBuilder.Render("PRINTER:", "definice.json") && tisk na výchozí tiskárnu
*--- XML z externího zdroje ---
loBuilder.RenderFromFiles("sestava.html", "definice.json", "data.xml")
Triple auto-detekce:
{ → string, jinak → cesta k souboru"document"/"blocks" → document, "report"/"sections" → table.html, .pdf, .xlsx*--- FRX XML export → JSON šablona (auto-detekce typu) ---
lcJson = loBuilder.ConvertFrxToJson(lcFrxXml)
*--- Všechny 3 soubory (JSON + vzorový XML + PRG) ---
loBuilder.ConvertFrxToAllFiles("C:\frx\CENIK.xml", "C:\output\")
*--- Vynucený typ (obchází auto-detekci) ---
loBuilder.ConvertFrxToDocumentJsonFile("C:\frx\INVUCKC.xml", "C:\output\invuckc.json")
loBuilder.ConvertFrxToTableReportJsonFile("C:\frx\CENIK.xml", "C:\output\cenik.json")
*--- Vynucený typ — všechny 3 soubory ---
loBuilder.ConvertFrxToDocumentAllFiles("C:\frx\INVUCKC.xml", "C:\output\")
loBuilder.ConvertFrxToTableReportAllFiles("C:\frx\CENIK.xml", "C:\output\")
*--- Vzorový XML a FoxPro PRG z JSON šablony ---
lcXml = loBuilder.GenerateSampleXmlFromJson(lcJson)
lcPrg = loBuilder.GenerateFoxProCodeFromJson(lcJson)
Auto-detekce typu (table vs. document) se řídí výškami bandů a počtem polí v FRX. Pro případy kdy detekce selže, existují varianty s vynuceným typem (TableReport/Document v názvu metody).
{element.field} v report propertiesVšechny string properties v JSON sekcích report, header, footer, pageHeader, pageFooter podporují {element.field} placeholdery. Resolvují se při renderování z XML dat.
{
"report": {
"orientation": "{parametry.orientace}",
"dataFontPt": "{parametry.fontpt}",
"culture": "{parametry.kultura}"
}
}
loBuilder.Push("parametry")
loBuilder.AddAttribute("orientace", "landscape")
loBuilder.AddAttribute("fontpt", "6")
loBuilder.AddAttribute("kultura", "en-US")
loBuilder.Pop()
loBuilder.Render("sestava.html", "definice.json")
&& → orientation=landscape, dataFontPt=6, culture=en-US
loBuilder.OpenReportEditor("C:\json\sablona.json", "C:\data\sample.xml")
Otevře VS Code s:
loBuilder.ShowProgress = .T.
loBuilder.Render("sestava.pdf", "sablona.json") && dialog s průběhem
loBuilder.ShowProgress = .F. && vypnout
WinForms dialog na samostatném STA threadu:
Řádek 450 / 1 200)HtmlErrorMessage, Render() vrátí false)Reset() i EndBatch() — nastaví se jednou, funguje pro všechny Render() volání